package com.atguigu.tingshu.account.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"all"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

	@Autowired
	private UserAccountMapper userAccountMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private UserAccountDetailMapper userAccountDetailMapper;
    /**
     * 新增用户账户余额
     * @param userId
     */
    @Override
    public void saveAccount(Long userId) {
        UserAccount userAccount = new UserAccount();
        userAccount.setUserId(userId);
        userAccountMapper.insert(userAccount);
    }

    @Override
    public BigDecimal getAvailableAmount(Long userId) {
        UserAccount userAccount = this.getUserAccount(userId);
        if (userAccount != null) {
            return userAccount.getAvailableAmount();
        }
        return null;
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public UserAccount getUserAccount(Long userId) {
        LambdaQueryWrapper<UserAccount> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserAccount::getUserId, userId);
        return userAccountMapper.selectOne(wrapper);
    }

    @Override
    public AccountLockResultVo checkAndLock(AccountLockVo accountLockVo, Long userId) {

        //1. 使用redis-setnx防止重复请求
        String key = RedisConstant.ACCOUNT_MULTIPLE_CHECK + accountLockVo.getOrderNo();
        String lockResultKey = RedisConstant.ACCOUNT_CHECK_DATA + accountLockVo.getOrderNo();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, accountLockVo.getOrderNo(), 1, TimeUnit.HOURS);
        if (!flag) {
            //不是第一次调用,尝试从Redis 中获取 账户锁定结果,如果有直接返回
            AccountLockResultVo accountLockResultVo = (AccountLockResultVo) redisTemplate.opsForValue().get(lockResultKey);
            if (accountLockResultVo == null) {
                //删除重复KEY 抛出异常
                redisTemplate.delete(key);
                throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR);
            }
        }
        //2. 核对账户可用金额并锁定账户数据(悲观锁)；查询返回的是满足要求的账户
        //根据用户id  + 锁定金额对账户表中进行查询 余额是否充足 利用数据库锁机制 select *  from  userAccount ..... for update 避免 并发情况下 对帐户多次锁定
        UserAccount  userAccount =    userAccountMapper.check(userId,accountLockVo.getAmount());

        //3. 用户没有可用账户或没有足够的金额，则删除锁定key，并返回信息提示
        if (userAccount == null) {
            //锁定失败
            redisTemplate.delete(key);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR);
        }
        //余额重组,执行锁定操作
         int count  = userAccountMapper.lock(userId,accountLockVo.getAmount());
        if (count == 0) {
            //锁定失败
            redisTemplate.delete(key);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR);
        }
        //检测锁定是否成工
        AccountLockResultVo accountLockResultVo =  BeanUtil.copyProperties(accountLockVo,AccountLockResultVo.class);
        redisTemplate.opsForValue().set(lockResultKey,accountLockResultVo,1, TimeUnit.HOURS);


        //4. 添加账户明细，写入user_account_detail 表中
        this.saveUserAccountDetail(accountLockVo.getUserId(), "锁定：" + accountLockVo.getContent(),
                SystemConstant.ACCOUNT_TRADE_TYPE_LOCK, accountLockVo.getAmount(), accountLockVo.getOrderNo());
        return accountLockResultVo;
    }

    @Override
    public void saveUserAccountDetail(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {
            UserAccountDetail userAccountDetail = new UserAccountDetail();
            userAccountDetail.setUserId(userId);
            userAccountDetail.setTitle(title);
            userAccountDetail.setTradeType(tradeType);
            userAccountDetail.setAmount(amount);
            userAccountDetail.setOrderNo(orderNo);
            userAccountDetailMapper.insert(userAccountDetail);
        }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void minus(String orderNo) {
        //幂等性处理 防止重复扣除金额 防止Kafka多次发送消息,重复消费
        String key = "account:lock:" + orderNo;
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
        if (flag) {
            //第一次提交
            //2.扣减金额 使用Redis里面的锁定金额
            String lockResultKey = RedisConstant.ACCOUNT_CHECK_DATA + orderNo;
            AccountLockResultVo accountLockResultVo = (AccountLockResultVo) redisTemplate.opsForValue().get(lockResultKey);
            if (accountLockResultVo == null) {
                //锁定结果失效
                //删除这个key防止 其他人进不来
                redisTemplate.delete(key);
                throw new GuiguException(ResultCodeEnum.ACCOUNT_MINUSLOCK_ERROR);
            }

            //执行扣减
          int count =userAccountMapper.minus(accountLockResultVo.getUserId(),accountLockResultVo.getAmount());
            if (count == 0) {
                redisTemplate.delete(key);
                throw new GuiguException(ResultCodeEnum.ACCOUNT_MINUSLOCK_ERROR);
            }

            //2.3 记录账户变动日志
            this.saveUserAccountDetail(accountLockResultVo.getUserId(),
                  "扣减"+  accountLockResultVo.getContent(), SystemConstant.ACCOUNT_TRADE_TYPE_MINUS,
                    accountLockResultVo.getAmount(), orderNo);

            //执行成功完毕删除 在redis的锁定金额
            //2.4 删除Redis中锁定结果
            redisTemplate.delete(lockResultKey);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unlock(String orderNo) {
        //1.业务去重避免多次扣减
        String key = "account:unlock" + orderNo;
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
        if (!flag) {
            return;
        }
        //2.从Redis中获取锁定结果用于恢复锁定金额
        String lockResultKey = RedisConstant.ACCOUNT_CHECK_DATA + orderNo;
        AccountLockResultVo accountLockResultVo = (AccountLockResultVo) redisTemplate.opsForValue().get(lockResultKey);
        if (accountLockResultVo == null) {
            return;
        }
        int count = userAccountMapper.unlock(accountLockResultVo.getUserId(), accountLockResultVo.getAmount());
        if (count == 0) {
            //更新失败 解除去重
            redisTemplate.delete(key);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_UNLOCK_ERROR);
        }
        //3.记录账户变动日志
        this.saveUserAccountDetail(accountLockResultVo.getUserId(), "解锁：" + accountLockResultVo.getContent(), SystemConstant.ACCOUNT_TRADE_TYPE_UNLOCK, accountLockResultVo.getAmount(), orderNo);
        //4.恢复锁定金额后，删除锁定结果，以防重复恢复
        redisTemplate.delete(lockResultKey);
    }

    @Override
    public IPage<UserAccountDetail> getUserRechargePage(Page<UserAccountDetail> pageParam, Long userId) {

        // 调用mapper 方法
        return userAccountDetailMapper.getUserRechargePage(pageParam,userId);
    }

    @Override
    public IPage<UserAccountDetail> getUserConsumePage(Page<UserAccountDetail> pageParam, Long userId) {
        // 调用mapper 层方法
        return userAccountDetailMapper.selectUserConsumePage(pageParam, userId);
    }


}
